<?php

namespace helper;

/*
-- ----------------------------
-- auth_rule，规则表
-- ----------------------------
DROP TABLE IF EXISTS `auth_rule`;
CREATE TABLE `auth_rule` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
  `pid` int(11) DEFAULT '0' COMMENT '上级ID',
  `module` char(30) NOT NULL DEFAULT '' COMMENT '使用模块',
  `title` varchar(255) NOT NULL DEFAULT '' COMMENT '规则中文名称',
  `name` varchar(180) NOT NULL DEFAULT '' COMMENT '规则标识',
  `icon` varchar(255) DEFAULT '' COMMENT '规则图标',
  `is_menu` tinyint(1) DEFAULT '1' COMMENT '是否菜单',
  `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态：为1正常，为0禁用',
  `desc` varchar(255) DEFAULT '' COMMENT '描述',
  `show_order` int(11) unsigned DEFAULT '0' COMMENT '排序',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=64 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='权限规则表';
-- ----------------------------
-- auth_group 用户组表
-- ----------------------------
DROP TABLE IF EXISTS `auth_group`;
CREATE TABLE `auth_group` (
    `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '规则唯一标识',
    `title` varchar(255) NOT NULL DEFAULT '' COMMENT '用户组中文名称',
    `module` char(30) NOT NULL DEFAULT '' COMMENT '使用模块',
    `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态：为1正常，为0禁用',
    `rules` varchar(3000) NOT NULL DEFAULT '' COMMENT '用户组拥有的规则id， 多个规则","隔开',
    PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4;
 */

use think\facade\Db;

class Auth
{
    //对象实例
    private static $instance;
    //默认配置
    protected $_config = array(
        'auth_on'    => true, //认证开关
        'auth_type'  => 1, //认证方式，1为时时认证,2为登录认证。登录认证需开启session
        'auth_group' => 'auth_group', //用户组数据表名
        'auth_rule'  => 'auth_rule', //权限规则表
    );

    /**
     * 初始化配置信息
     * AuthUtil constructor.
     */
    public function __construct()
    {
        if (config('?auth')) {
            $this->_config = array_merge($this->_config, config('auth'));
        }
    }

    /**
     * 实例化接口
     * @return static
     */
    public static function instance()
    {
        if (is_null(self::$instance)) {
            self::$instance = new static();
        }
        return self::$instance;
    }

    /**
     * 获取对应分组权限id
     * @param int $groupId
     * @return array
     * @throws \Exception
     */
    public function getAuthIds(int $groupId)
    {
        return Db::table($this->_config['auth_group'])
                 ->where('id', $groupId)
                 ->where('status', 1)
                 ->value('rules');
    }

    /**
     * 获取用户拥有权限列表
     * @param int $groupId
     * @return array|mixed
     * @throws \Exception
     */
    public function getAuthRules(int $groupId)
    {
        $cacheKey = "_auth_list_{$groupId}";
        if ($this->_config['auth_type'] == 2 && session("?{$cacheKey}")) return session("{$cacheKey}");
        $ids = $this->getAuthIds($groupId);
        if (empty($ids)) return [];
        $authRules = Db::table($this->_config['auth_rule'])
                       ->where('status', 1)
                       ->where('id', 'in', $ids)
                       ->column('name');
        if ($this->_config['auth_type'] == 2) session("{$cacheKey}", $authRules);
        return $authRules;
    }

    /**
     * 检测权限
     * @param $name //可以是字符串或数组或逗号分割
     * @param array $authRules 用户拥有权限组
     * @param string $mode 执行check的模式 1:url模式 2:功能模式
     * @param string $relation //$or 是否为or关系，为true时name为数组，只要数组中有一个条件通过则通过，如果为false需要全部条件通过。
     * @return bool
     * @throws \Exception
     */
    public function checkAuth($name, array $authRules, $mode = 'url', $relation = 'or')
    {
        if (!$this->_config['auth_on']) return true;
        if (empty($authRules)) return false;
        if (is_string($name)) {
            $name = strtolower($name);
            if (strpos($name, ',') !== false) {
                $name = explode(',', $name);
            } else {
                $name = array($name);
            }
        }
        $list    = []; //保存验证通过的规则名
        $request = [];
        if ('url' == $mode) $request = unserialize(strtolower(serialize(request()->get())));
        foreach ($authRules as $auth) {
            $query = preg_replace('/^.+\?/U', '', $auth);
            if ('url' == $mode && $query != $auth) {
                parse_str($query, $param); //query转数组
                $intersect = array_intersect_assoc($request, $param);
                $auth      = preg_replace('/\?.*$/U', '', $auth);
                if (in_array($auth,$name) && $intersect == $param) $list[] = $auth; //如果节点相符且url参数满足
            } else { //不检测url参数，将query参数去除
                foreach($name as $k=>$v){
                    $name[$k] = parse_url($v)['path'];
                }
                if (in_array($auth, $name)) $list[] = $auth;
            }
        }
        if ($relation == 'or' and !empty($list)) return true;
        $diff = array_diff($name, $list);
        if ($relation == 'and' and empty($diff)) return true;
        return false;
    }

    /**
     * 按groupId检测权限
     * @param $name
     * @param int $groupId
     * @param string $mode
     * @param string $relation
     * @return bool
     * @throws \Exception
     */
    public function check($name, int $groupId, $mode = 'url', $relation = 'or')
    {
        $authNames = $this->getAuthRules($groupId);
        return $this->checkAuth($name, $authNames, $mode, $relation);
    }

    /**
     * 按token校验权限
     * @param $name
     * @param string $token session键名，jwt解析后需存在‘group_id’键
     * @param string $mode
     * @param string $relation
     * @return bool
     */
    public function checkToken($name, string $token, $mode = 'url', $relation = 'or')
    {
        if (empty($token)) return false;
        try {
            JwtHelper::make()->verfiyToken($token);
            $payLoad   = JwtHelper::make()->decodeData();
            $authRules = $this->getAuthRules($payLoad['group_id']);
            return $this->checkAuth($name, $authRules, $mode, $relation);
        } catch (\Exception $e) {
            return false;
        }
    }

}